using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

using Sandia.MVCFramework.Common;
using Sandia.MVCFramework.Controller;
using Sandia.MVCFramework.DataAccessor;
using Sandia.MVCFramework.DataAnalyzer;
using Sandia.MVCFramework.DataStore;
using Sandia.MVCFramework.ViewManager;
using Sandia.MVCFramework.Plugin;
using Sandia.MVCFramework.TestPlugin.Data;
using Sandia.MVCFramework.TestPlugin.DataAccessor;
using Sandia.MVCFramework.TestPlugin.DataAnalyzer;
using Sandia.MVCFramework.TestPlugin.DataStore;
using Sandia.MVCFramework.TestPlugin.Request;
using Sandia.MVCFramework.TestPlugin.ViewManager;

using NUnit.Framework;

namespace Sandia.MVCFramework.Test
{
    [TestFixture]
    public class PluginTests : FailOnErrorLoggingTestFixture
    {
        [Test]
        public void StandardPluginSourceTest()
        {
            StandardPluginSource pluginSource = 
                new StandardPluginSource(
                    Directory.GetParent(Directory.GetCurrentDirectory()).FullName);
            Assert.IsTrue(
                pluginSource.AvailablePlugins.Count == 1, 
                "Incorrect number of plugins were loaded.");

            FileInfo fileInfo =
                new FileInfo(
                    Path.Combine(
                        Directory.GetCurrentDirectory(),
                        "Sandia.MVCFramework.TestPlugin.dll"));
            Assembly assembly = Assembly.LoadFile(fileInfo.FullName);

            Assert.AreEqual(
                pluginSource.AvailablePlugins[0].GetType(), 
                typeof(PluginImplementation),
                "Plugin types do not match.");
        }

        [Test]
        public void AbstractStandardPluginTest()
        {
            // PluginImplementation extends AbstractStandardPlugin
            PluginImplementation plugin = new PluginImplementation();
            DummyController controller = new DummyController();
            plugin.Init(controller, controller);

            // Check request routings
            Assert.IsTrue(
                plugin.RequestRoutings[typeof(ARequest)].Count == 2,
                "Incorrect number of ARequest routings");
            Assert.AreEqual(
                plugin.RequestRoutings[typeof(ARequest)][0], 
                plugin.DataAnalyzers[0],
                "DataAnalyzer request routings did not match");
            Assert.AreEqual(
                plugin.RequestRoutings[typeof(ARequest)][1], 
                plugin.DataStores[0],
                "DataStore request routings did not match");

            Assert.IsTrue(
                plugin.RequestRoutings[typeof(BRequest)].Count == 1,
                "Incorrect number of BRequest routings");
            Assert.AreEqual(
                plugin.RequestRoutings[typeof(BRequest)][0], 
                plugin.DataAccessors[0],
                "DataAccessor request routings did not match");

            Assert.IsTrue(
                plugin.RequestRoutings[typeof(CRequest)].Count == 1,
                "Incorrect number of CRequest routings");
            Assert.AreEqual(
                plugin.RequestRoutings[typeof(CRequest)][0], 
                plugin.DataStores[0],
                "DataStore request routings did not match");

            Assert.IsTrue(
                plugin.RequestRoutings[typeof(DRequest)].Count == 1,
                "Incorrect number of DRequest routings");
            Assert.AreEqual(
                plugin.RequestRoutings[typeof(DRequest)][0], 
                plugin.DataAccessors[0],
                "DataAccessor request routings did not match");

            // Check subscriptions
            Assert.IsTrue(
                plugin.Subscriptions[typeof(AOneData)].Count == 2,
                "Incorrect number of AOneData subscriptions");
            Assert.AreEqual(
                plugin.Subscriptions[typeof(AOneData)][0], 
                plugin.DataAnalyzers[0],
                "DataAnalyzer subscriptions do not match");
            Assert.AreEqual(
                plugin.Subscriptions[typeof(AOneData)][1], 
                plugin.DataStores[0],
                "DataStore subscriptions do not match");

            Assert.IsTrue(
                !plugin.Subscriptions.ContainsKey(typeof(ATwoData)),
                "Incorrect number of ATwoData subscriptions");

            Assert.IsTrue(
                plugin.Subscriptions[typeof(BData)].Count == 1,
                "Incorrect number of BData subscriptions");
            Assert.AreEqual(
                plugin.Subscriptions[typeof(BData)][0], 
                plugin.DataStores[0],
                "DataStore subscriptions do not match");

            Assert.IsTrue(
                plugin.Subscriptions[typeof(CData)].Count == 1,
                "Incorrect number of CData subscriptions");
            Assert.AreEqual(
                plugin.Subscriptions[typeof(CData)][0], 
                plugin.DataAnalyzers[0],
                "DataAnalyzer subscriptions do not match");

            Assert.IsTrue(
                plugin.Subscriptions[typeof(DData)].Count == 1,
                "Incorrect number of DData subscriptions");
            Assert.AreEqual(
                plugin.Subscriptions[typeof(DData)][0], 
                plugin.ViewManagers[0],
                "ViewManager subscriptions do not match");
        }

        [Test]
        public void StandardPluginLoaderTest()
        {
            FileInfo fileInfo =
                new FileInfo(
                    Path.Combine(
                        Directory.GetCurrentDirectory(),
                        "Sandia.MVCFramework.TestPlugin.dll"));
            Assembly assembly = Assembly.LoadFile(fileInfo.FullName);

            //Type dummyDataAccessorType =
            //    assembly.GetType("Sandia.MVCFramework.TestPlugin.DataAccessor.DummyDataAccessor");
            //Type dummyDataAnalyzerType =
            //    assembly.GetType("Sandia.MVCFramework.TestPlugin.DataAnalyzer.DummyDataAnalyzer");
            //Type dummyDataStoreType =
            //    assembly.GetType("Sandia.MVCFramework.TestPlugin.DataStore.DummyDataStore");
            //Type dummyViewManagerType =
            //    assembly.GetType("Sandia.MVCFramework.TestPlugin.ViewManager.DummyViewManager");

            Type dummyDataAccessorType =
                typeof(DummyDataAccessor);
            Type dummyDataAnalyzerType =
                typeof(DummyDataAnalyzer);
            Type dummyDataStoreType =
                typeof(DummyDataStore);
            Type dummyViewManagerType =
                typeof(DummyViewManager);

            StandardPluginSource pluginSource =
                new StandardPluginSource(
                    Directory.GetParent(Directory.GetCurrentDirectory()).FullName);
            StandardPluginLoader pluginLoader = 
                new StandardPluginLoader(pluginSource);
            DummyController controller = new DummyController();
            pluginLoader.LoadPlugins(controller);
            Assert.IsTrue(
                controller.DataAccessors.Count == 1,
                "Incorrect number of DataAccessors");
            Assert.AreEqual(
                controller.DataAccessors[0].GetType(),
                dummyDataAccessorType,
                "DataAccessor types do not match");
            Assert.IsTrue(
                controller.DataAnalyzers.Count == 1,
                "Incorrect number of DataAnalyzers");
            Assert.AreEqual(
                controller.DataAnalyzers[0].GetType(),
                dummyDataAnalyzerType,
                "DataAnalyzer types do not match");
            Assert.IsTrue(
                controller.DataStores.Count == 1,
                "Incorrect number of DataStores");
            Assert.AreEqual(
                controller.DataStores[0].GetType(),
                dummyDataStoreType,
                "DataStore types do not match");
            Assert.IsTrue(
                controller.ViewManagers.Count == 1,
                "Incorrect number of ViewManagers");
            Assert.AreEqual(
                controller.ViewManagers[0].GetType(),
                dummyViewManagerType,
                "ViewManager types do not match");

            //// Get request types
            //Type aRequestType = 
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Request.ARequest");
            //Type bRequestType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Request.BRequest");
            //Type cRequestType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Request.CRequest");
            //Type dRequestType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Request.DRequest");

            //// Get data types
            //Type aOneDataType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Data.AOneData");
            //Type aTwoDataType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Data.ATwoData");
            //Type bDataType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Data.BData");
            //Type cDataType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Data.CData");
            //Type dDataType =
            //    assembly.GetType(
            //        "Sandia.MVCFramework.TestPlugin.Data.DData");

            //// Check request routings
            //Assert.IsTrue(
            //    controller.RequestRoutings[aRequestType].Count == 2,
            //    "Incorrect number of ARequest routings");
            //Assert.AreEqual(
            //    controller.RequestRoutings[aRequestType][0],
            //    controller.DataAnalyzers[0],
            //    "DataAnalyzer request routings did not match");
            //Assert.AreEqual(
            //    controller.RequestRoutings[aRequestType][1], 
            //    controller.DataStores[0],
            //    "DataStore request routings did not match");

            //Assert.IsTrue(
            //    controller.RequestRoutings[bRequestType].Count == 1,
            //    "Incorrect number of BRequest routings");
            //Assert.AreEqual(
            //    controller.RequestRoutings[bRequestType][0],
            //    controller.DataAccessors[0],
            //    "DataAccessor request routings did not match");

            //Assert.IsTrue(
            //    controller.RequestRoutings[cRequestType].Count == 1,
            //    "Incorrect number of CRequest routings");
            //Assert.AreEqual(
            //    controller.RequestRoutings[cRequestType][0],
            //    controller.DataStores[0],
            //    "DataStore request routings did not match");

            //Assert.IsTrue(
            //    controller.RequestRoutings[dRequestType].Count == 1,
            //    "Incorrect number of DRequest routings");
            //Assert.AreEqual(
            //    controller.RequestRoutings[dRequestType][0],
            //    controller.DataAccessors[0],
            //    "DataAccessor request routings did not match");

            //// Check subscriptions
            //Assert.IsTrue(
            //    controller.Subscriptions[aOneDataType].Count == 2,
            //    "Incorrect number of AOneData subscriptions");
            //Assert.AreEqual(
            //    controller.Subscriptions[aOneDataType][0],
            //    controller.DataAnalyzers[0],
            //    "DataAnalyzer subscriptions do not match");
            //Assert.AreEqual(
            //    controller.Subscriptions[aOneDataType][1],
            //    controller.DataStores[0],
            //    "DataStore subscriptions do not match");

            //Assert.IsTrue(
            //    !controller.Subscriptions.ContainsKey(aTwoDataType),
            //    "Incorrect number of ATwoData subscriptions");

            //Assert.IsTrue(
            //    controller.Subscriptions[bDataType].Count == 1,
            //    "Incorrect number of BData subscriptions");
            //Assert.AreEqual(
            //    controller.Subscriptions[bDataType][0],
            //    controller.DataStores[0],
            //    "DataStore subscriptions do not match");
            
            //Assert.IsTrue(
            //    controller.Subscriptions[cDataType].Count == 1,
            //    "Incorrect number of CData subscriptions");
            //Assert.AreEqual(
            //    controller.Subscriptions[cDataType][0],
            //    controller.DataAnalyzers[0],
            //    "DataAnalyzer subscriptions do not match");

            //Assert.IsTrue(
            //    controller.Subscriptions[dDataType].Count == 1,
            //    "Incorrect number of DData subscriptions");
            //Assert.AreEqual(
            //    controller.Subscriptions[dDataType][0],
            //    controller.ViewManagers[0],
            //    "ViewManager subscriptions do not match");

            // Check request routings
            Assert.IsTrue(
                controller.RequestRoutings[typeof(ARequest)].Count == 2,
                "Incorrect number of ARequest routings");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(ARequest)][0],
                controller.DataAnalyzers[0],
                "DataAnalyzer request routings did not match");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(ARequest)][1],
                controller.DataStores[0],
                "DataStore request routings did not match");

            Assert.IsTrue(
                controller.RequestRoutings[typeof(BRequest)].Count == 1,
                "Incorrect number of BRequest routings");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(BRequest)][0],
                controller.DataAccessors[0],
                "DataAccessor request routings did not match");

            Assert.IsTrue(
                controller.RequestRoutings[typeof(CRequest)].Count == 1,
                "Incorrect number of CRequest routings");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(CRequest)][0],
                controller.DataStores[0],
                "DataStore request routings did not match");

            Assert.IsTrue(
                controller.RequestRoutings[typeof(DRequest)].Count == 1,
                "Incorrect number of DRequest routings");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(DRequest)][0],
                controller.DataAccessors[0],
                "DataAccessor request routings did not match");

            // Check subscriptions
            Assert.IsTrue(
                controller.Subscriptions[typeof(AOneData)].Count == 2,
                "Incorrect number of AOneData subscriptions");
            Assert.AreEqual(
                controller.Subscriptions[typeof(AOneData)][0],
                controller.DataAnalyzers[0],
                "DataAnalyzer subscriptions do not match");
            Assert.AreEqual(
                controller.Subscriptions[typeof(AOneData)][1],
                controller.DataStores[0],
                "DataStore subscriptions do not match");

            Assert.IsTrue(
                !controller.Subscriptions.ContainsKey(typeof(ATwoData)),
                "Incorrect number of ATwoData subscriptions");

            Assert.IsTrue(
                controller.Subscriptions[typeof(BData)].Count == 1,
                "Incorrect number of BData subscriptions");
            Assert.AreEqual(
                controller.Subscriptions[typeof(BData)][0],
                controller.DataStores[0],
                "DataStore subscriptions do not match");

            Assert.IsTrue(
                controller.Subscriptions[typeof(CData)].Count == 1,
                "Incorrect number of CData subscriptions");
            Assert.AreEqual(
                controller.Subscriptions[typeof(CData)][0],
                controller.DataAnalyzers[0],
                "DataAnalyzer subscriptions do not match");

            Assert.IsTrue(
                controller.Subscriptions[typeof(DData)].Count == 1,
                "Incorrect number of DData subscriptions");
            Assert.AreEqual(
                controller.Subscriptions[typeof(DData)][0],
                controller.ViewManagers[0],
                "ViewManager subscriptions do not match");

            // Test plugin removal
            pluginLoader.RemovePlugins(controller);

            Assert.AreEqual(
                controller.DataAccessors.Count,
                0,
                "DataAccessors not removed correctly.");
            Assert.AreEqual(
                controller.DataAnalyzers.Count,
                0,
                "DataAnalyzers not removed correctly.");
            Assert.AreEqual(
                controller.DataStores.Count,
                0,
                "DataStores not removed correctly.");
            Assert.AreEqual(
                controller.ViewManagers.Count,
                0,
                "ViewManagers not removed correctly.");
            Assert.AreEqual(
                controller.RequestRoutings.Count,
                5,
                "RequestRoutings not removed correctly.");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(ARequest)].Count,
                0,
                "RequestRoutings not removed correctly.");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(BRequest)].Count,
                0,
                "RequestRoutings not removed correctly.");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(CRequest)].Count,
                0,
                "RequestRoutings not removed correctly.");
            Assert.AreEqual(
                controller.RequestRoutings[typeof(DRequest)].Count,
                0,
                "RequestRoutings not removed correctly.");
            Assert.AreEqual(
                controller.Subscriptions.Count,
                4,
                "Subscriptions not removed correctly.");
            Assert.AreEqual(
                controller.Subscriptions[typeof(AOneData)].Count,
                0,
                "Subscriptions not removed correctly.");
            Assert.AreEqual(
                controller.Subscriptions[typeof(BData)].Count,
                0,
                "Subscriptions not removed correctly.");
            Assert.AreEqual(
                controller.Subscriptions[typeof(CData)].Count,
                0,
                "Subscriptions not removed correctly.");
            Assert.AreEqual(
                controller.Subscriptions[typeof(DData)].Count,
                0,
                "Subscriptions not removed correctly.");
        }

        /// <summary>
        /// Note, this code is also in PluginTests.cs
        /// </summary>
        /// <param name="dirA"></param>
        /// <param name="dirB"></param>
        void CompareDirectories(string dirA, string dirB)
        {
            DirectoryInfo dir1 = new DirectoryInfo(dirA);
            DirectoryInfo dir2 = new DirectoryInfo(dirB);

            // Get a list of all of the entries (files and directories) in the source and the target
            //
            FileSystemInfo[] entries1 = dir1.GetFileSystemInfos();
            FileSystemInfo[] entries2 = dir2.GetFileSystemInfos();

            // While we can use the built-in iterator for one list, we have to create a "manual" iterator for the other list
            //
            System.Collections.IEnumerator iter2 = entries2.GetEnumerator();

            // Iterate through the entries in the list from the source
            //
            foreach (FileSystemInfo anEntry1 in entries1)
            {
                if (anEntry1.Name.CompareTo("CVS") == 0) continue; // skip the CVS folder
                // It is important to note that we must not disturb the second iterator here.

            RetryEntry2: // a sort of loop construct, the Assert will cause an exit

                // Try to move to the next entry in the list from the target
                //
                bool yesNext = iter2.MoveNext();

                // Test to ensure that there was indeed a next entry in the target's list
                //
                Assert.IsTrue(yesNext, "Inconsistent entries in both source and target.");

                // Get the entry
                //
                FileSystemInfo anEntry2 = (FileSystemInfo)iter2.Current;

                if (anEntry2.Name.CompareTo("CVS") == 0) goto RetryEntry2; //skip the CVS folder

                // Test to ensure that these entries from the source and the target correspond
                //
                Assert.AreEqual(anEntry1.Name, anEntry2.Name, "Directory entries don't match exactly.");

                if ( (anEntry1.Attributes & FileAttributes.Directory) == FileAttributes.Directory &&
                     (anEntry2.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                {
                    this.CompareDirectories( Path.Combine(dirA,anEntry1.ToString()), Path.Combine(dirB,anEntry2.ToString()) ); //recurse
                }
            }
        }

        /// <summary>
        /// Test the StandardPluginUpdater
        /// </summary>
        [Test]
        public void StandardPluginUpdaterTest()
        {
            DirectoryInfo currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());

            DirectoryInfo parentDirectory = currentDirectory.Parent.Parent;

            string sourceDirectory = Path.Combine(parentDirectory.FullName, "TestDirectory1");
            string pluginDirectory = Path.Combine(parentDirectory.FullName, "TestDirectory5");

            DirectoryInfo testDirectory4 = new DirectoryInfo(Path.Combine(parentDirectory.FullName, "TestDirectory4"));
            DirectoryInfo testDirectory5 = new DirectoryInfo(pluginDirectory);

            FileSystemTools.Copy(testDirectory4, testDirectory5, true);

            StandardPluginUpdater updater = new StandardPluginUpdater(sourceDirectory, pluginDirectory);

            Assert.IsTrue(updater.UpdateRequired);

            updater.Update();

            DirectoryInfo pluginDirInfo = new DirectoryInfo(pluginDirectory);

            CompareDirectories(sourceDirectory.ToString(), pluginDirectory.ToString());

            // Delete the target directory
            //
            testDirectory5.Delete(true);
        }

        public class DummyController : StandardController
        {
            public IList<IDataAccessor> DataAccessors
            {
                get
                {
                    return dataAccessors;
                }
            }

            public IList<IDataAnalyzer> DataAnalyzers
            {
                get
                {
                    return dataAnalyzers;
                }
            }

            public IList<IDataStore> DataStores
            {
                get
                {
                    return dataStores;
                }
            }

            public IList<IViewManager> ViewManagers
            {
                get
                {
                    return viewManagers;
                }
            }

            public Dictionary<Type, List<IRequestProcessor>> RequestRoutings
            {
                get
                {
                    return this.requestRoutings;
                }
            }

            public Dictionary<Type, List<ISubscriber>> Subscriptions
            {
                get
                {
                    Dictionary<Type, List<ISubscriber>> subscriptions =
                        new Dictionary<Type, List<ISubscriber>>();
                    foreach (KeyValuePair<Type, Dictionary<ISubscriber, bool>> pair in this.dataSubscriptions)
                    {
                        if (!subscriptions.ContainsKey(pair.Key))
                        {
                            subscriptions[pair.Key] = new List<ISubscriber>();
                        }
                        foreach (ISubscriber subscriber in pair.Value.Keys)
                        {
                            subscriptions[pair.Key].Add(subscriber);
                        }
                    }
                    return subscriptions;
                }
            }
        }
    }
}